package com.leilei.sensitive;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import static com.leilei.sensitive.SensitiveAutoConfiguration.printReverseLog;

/**
 * @author: lei
 * @date: 2024-08-28 9:21
 * @desc: 逆脱敏处理器 将前端传递的脱敏数据反转为数据库已有数据
 */
@Slf4j
public class ReverseSensitiveDataHandler {

    private static final Map<Class<?>, List<Field>> FIELD_CACHE = new ConcurrentHashMap<>();

    private static final Map<Class<?>, Field> ID_FIELD_CACHE = new ConcurrentHashMap<>();

    /**
     * 逆脱敏处理,自动提取主键ID并根据主键ID查询数据库
     *
     * @param mapper
     * @param updatedObj
     * @return T
     * @author lei
     * @date 2025-05-22 10:10:07
     */
    public static <T> T handleAutoDetectId(BaseMapper<T> mapper, T updatedObj, boolean throwException) {
        if (updatedObj == null || mapper == null) {
            return updatedObj;
        }
        Class<?> entityClass = updatedObj.getClass();
        List<Field> fields = ReverseSensitiveDataHandler.resolveReverseField(entityClass);
        if (fields.isEmpty()) {
            return updatedObj;
        }
        Object id = extractIdValue(updatedObj);
        if (id == null) {
            if (throwException) {
                throw new IllegalArgumentException(String.format("无法提取主键ID，请确认实体类%s中含有 @TableId 或字段名为 id", updatedObj));
            }
            return updatedObj;
        }
        Function<Object, T> queryFunc = key -> mapper.selectById((Serializable) key);
        T originalData = queryFunc.apply(id);
        if (originalData == null) {
            if (throwException) {
                throw new IllegalStateException(String.format("原始对象[%s]查询ID %s不存在，无法执行逆脱敏处理", updatedObj.getClass(), id));
            }
            return updatedObj;
        }
        return handleSensitiveData(originalData, updatedObj);
    }

    /**
     * 处理敏感信息，返璞归真
     *
     * @param originalData 未脱敏数据
     * @param updatedData  已脱敏数据
     * @return T
     * @author lei
     * @date 2024-08-28 10:41:55
     */
    public static <T> T handleSensitiveData(T originalData, T updatedData) {
        if (originalData == null || updatedData == null) {
            return updatedData;
        }
        Class<?> clazz = updatedData.getClass();
        List<Field> fields = resolveReverseField(clazz);
        if (fields.isEmpty()) {
            return updatedData;
        }
        for (Field field : fields) {
            ReverseSensitive reverseSensitive = field.getAnnotation(ReverseSensitive.class);
            if (reverseSensitive == null) {
                continue;
            }
            try {
                String updatedVal = (String) field.get(updatedData);
                String originalVal = (String) field.get(originalData);
                if (updatedVal == null || originalVal == null) {
                    continue;
                }
                if (Objects.equals(originalVal, updatedVal)) {
                    continue;
                }
                if (isMasked(updatedVal)) {
                    field.set(updatedData, originalVal);
                    if (printReverseLog && log.isDebugEnabled()) {
                        log.debug("[ReverseSensitive] 还原字段: {} = {} → {}", getDetailFieldName(field, clazz), updatedVal, originalVal);
                    }
                }

            } catch (IllegalAccessException e) {
                log.error("逆脱敏异常,字段{}访问失败 ", getDetailFieldName(field, clazz), e);
                throw new RuntimeException("逆脱敏异常,字段访问失败: " + getDetailFieldName(field, clazz));
            }
        }
        return updatedData;
    }

    @NotNull
    private static String getDetailFieldName(Field field, Class<?> clazz) {
        return clazz.getName() + "." + field.getName();
    }


    /**
     * 解析出需要逆脱敏的字段
     *
     * @param clazz
     * @return List<Field>
     * @author lei
     * @date 2025-05-21 15:28:10
     */
    public static List<Field> resolveReverseField(Class<?> clazz) {
        return FIELD_CACHE.computeIfAbsent(clazz, key -> {
            List<Field> result = new ArrayList<>();
            while (key != null && key != Object.class) {
                for (Field field : key.getDeclaredFields()) {
                    if (field.getType().equals(String.class) && field.getAnnotation(ReverseSensitive.class) != null) {
                        field.setAccessible(true);
                        result.add(field);
                    }
                }
                key = key.getSuperclass();
            }
            return result;
        });
    }


    /**
     * 判断是否脱敏,满足脱敏条件则返回true
     *
     * @param value
     * @return boolean
     * @author lei
     * @date 2025-05-21 16:22:55
     */
    private static boolean isMasked(String value) {
        if (value == null) {
            return false;
        }
        int maskCount = 0;
        for (char c : value.toCharArray()) {
            if (c == '*') {
                maskCount++;
            }
        }
        return maskCount >= SensitiveProperties.MIN_MSK_LENGTH;
    }

    /**
     * 拿到主键字段值
     *
     * @param obj
     * @return Object
     * @author lei
     * @date 2025-05-22 10:22:42
     */
    public static Object extractIdValue(Object obj) {
        Class<?> clazz = obj.getClass();
        Field idField = ID_FIELD_CACHE.computeIfAbsent(clazz, ReverseSensitiveDataHandler::resolveIdField);
        if (idField == null) {
            return null;
        }
        try {
            return idField.get(obj);
        } catch (IllegalAccessException e) {
            log.error("{}无法获取主键字段", obj, e);
            return null;
        }
    }

    /**
     * 获取主键所在字段
     *
     * @param clazz
     * @return Field
     * @author lei
     * @date 2025-05-22 10:23:03
     */
    private static Field resolveIdField(Class<?> clazz) {
        while (clazz != null && clazz != Object.class) {
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(com.baomidou.mybatisplus.annotation.TableId.class)) {
                    field.setAccessible(true);
                    return field;
                }
            }
            clazz = clazz.getSuperclass();
        }
        return null;
    }

}
